import { test, expect } from "@playwright/test";

import {
  createFixture,
  js,
  createAppFixture,
} from "./helpers/create-fixture.js";
import { PlaywrightFixture } from "./helpers/playwright-fixture.js";

test(
  "expect to be able to browse backward out of a remix app, then forward " +
    "twice in history and have pages render correctly",
  async ({ page, browserName }) => {
    test.skip(
      browserName === "firefox",
      "FireFox doesn't support browsing to an empty page (aka about:blank)",
    );

    let fixture = await createFixture({
      files: {
        "app/routes/_index.tsx": js`
          import { Link } from "react-router";

          export default function Index() {
            return (
              <div>
                <div id="pizza">pizza</div>
                <Link to="/burgers">burger link</Link>
              </div>
            )
          }
        `,

        "app/routes/burgers.tsx": js`
          export default function Index() {
            return <div id="cheeseburger">cheeseburger</div>;
          }
        `,
      },
    });

    // This creates an interactive app using puppeteer.
    let appFixture = await createAppFixture(fixture);

    let app = new PlaywrightFixture(appFixture, page);

    // Slow down the entry chunk on the second load so the bug surfaces
    let isSecondLoad = false;
    await page.route(/entry/, async (route) => {
      if (isSecondLoad) {
        await new Promise((r) => setTimeout(r, 1000));
      }
      route.continue();
    });

    // This sets up the Remix modules cache in memory, priming the error case.
    await app.goto("/");
    await app.clickLink("/burgers");
    expect(await page.content()).toContain("cheeseburger");
    await page.goBack();
    await page.waitForSelector("#pizza");
    expect(await app.getHtml()).toContain("pizza");

    // Takes the browser out of the Remix app
    await page.goBack();
    expect(page.url()).toContain("about:blank");

    // Forward to / and immediately again to /burgers.  This will trigger the
    // error since we'll load __routeModules for / but then try to hydrate /burgers
    isSecondLoad = true;
    await page.goForward();
    await page.goForward();
    await page.waitForSelector("#cheeseburger");

    // If we resolve the error, we should hard reload and eventually
    // successfully render /burgers
    await page.waitForSelector("#cheeseburger");
    expect(await app.getHtml()).toContain("cheeseburger");

    appFixture.close();
  },
);

test("allows users to pass a client side context to HydratedRouter", async ({
  page,
}) => {
  let fixture = await createFixture({
    files: {
      "app/entry.client.tsx": js`
        import { createContext, RouterContextProvider } from "react-router";
        import { HydratedRouter } from "react-router/dom";
        import { startTransition, StrictMode } from "react";
        import { hydrateRoot } from "react-dom/client";

        export const myContext = new createContext('foo');

        startTransition(() => {
          hydrateRoot(
            document,
            <StrictMode>
              <HydratedRouter
                getContext={() => {
                  return new RouterContextProvider([
                    [myContext, 'bar']
                  ]);
                }}
              />
            </StrictMode>
          );
        });
      `,
      "app/routes/_index.tsx": js`
        import { myContext } from "../entry.client";

        export function clientLoader({ context }) {
          return context.get(myContext);
        }
        export default function Index({ loaderData }) {
          return <h1>Hello, {loaderData}</h1>
        }
      `,
    },
  });

  let appFixture = await createAppFixture(fixture);
  let app = new PlaywrightFixture(appFixture, page);
  await app.goto("/", true);
  expect(await app.getHtml()).toContain("Hello, bar");

  appFixture.close();
});

test("allows users to pass an onError function to HydratedRouter", async ({
  page,
  browserName,
}) => {
  let fixture = await createFixture({
    files: {
      "app/entry.client.tsx": js`
        import { HydratedRouter } from "react-router/dom";
        import { startTransition, StrictMode } from "react";
        import { hydrateRoot } from "react-dom/client";

        startTransition(() => {
          hydrateRoot(
            document,
            <StrictMode>
              <HydratedRouter
                unstable_onError={(error, errorInfo) => {
                  console.log(error.message, JSON.stringify(errorInfo))
                }}
              />
            </StrictMode>
          );
        });
      `,
      "app/routes/_index.tsx": js`
        import { Link } from "react-router";
        export default function Index() {
          return <Link to="/page">Go to Page</Link>;
        }
      `,
      "app/routes/page.tsx": js`
        export default function Page() {
          throw new Error("Render error");
        }
        export function ErrorBoundary({ error }) {
          return <h1 data-error>Error: {error.message}</h1>
        }
      `,
    },
  });

  let logs: string[] = [];
  page.on("console", (msg) => logs.push(msg.text()));

  let appFixture = await createAppFixture(fixture);
  let app = new PlaywrightFixture(appFixture, page);

  await app.goto("/", true);
  await page.click('a[href="/page"]');
  await page.waitForSelector("[data-error]");

  expect(await app.getHtml()).toContain("Error: Render error");
  expect(logs.length).toBe(2);
  // First one is react logging the error
  if (browserName === "firefox") {
    expect(logs[0]).toContain("Error");
  } else {
    expect(logs[0]).toContain("Error: Render error");
  }
  expect(logs[0]).not.toContain("componentStack");
  // Second one is ours
  expect(logs[1]).toContain("Render error");
  expect(logs[1]).toContain('"componentStack":');

  appFixture.close();
});

test("allows users to instrument the client side router via HydratedRouter", async ({
  page,
}) => {
  let fixture = await createFixture({
    files: {
      "app/entry.client.tsx": js`
        import { HydratedRouter } from "react-router/dom";
        import { startTransition, StrictMode } from "react";
        import { hydrateRoot } from "react-dom/client";

        startTransition(() => {
          hydrateRoot(
            document,
            <StrictMode>
              <HydratedRouter
                unstable_instrumentations={[{
                  router(router) {
                    router.instrument({
                      async navigate(impl, info) {
                        console.log("start navigate", JSON.stringify(Object.entries(info).sort()));
                        await impl();
                        console.log("end navigate", JSON.stringify(Object.entries(info).sort()));
                      },
                      async fetch(impl, info) {
                        console.log("start fetch", JSON.stringify(Object.entries(info).sort()));
                        await impl();
                        console.log("end fetch", JSON.stringify(Object.entries(info).sort()));
                      }
                    })
                  },
                  route(route) {
                    route.instrument({
                      async loader(impl, info) {
                        let path = new URL(info.request.url).pathname;
                        console.log("start loader", route.id, path);
                        await impl();
                        console.log("end loader", route.id, path);
                      },
                      async action(impl, info) {
                        let path = new URL(info.request.url).pathname;
                        console.log("start action", route.id, path);
                        await impl();
                        console.log("end action", route.id, path);
                      }
                    })
                  }
                }]}
              />
            </StrictMode>
          );
        });
      `,
      "app/routes/_index.tsx": js`
        import { Link } from "react-router";
        export default function Index() {
          return <Link to="/page">Go to Page</Link>;
        }
      `,
      "app/routes/page.tsx": js`
        import { useFetcher } from "react-router";
        export function loader() {
          return { data: "hello world" };
        }
        export function action() {
          return "OK";
        }
        export default function Page({ loaderData }) {
          let fetcher = useFetcher({ key: 'a' });
          return (
            <>
              <h1 data-page>{loaderData.data}</h1>;
              <button data-fetch onClick={() => fetcher.submit({ key: 'value' }, {
                method: 'post',
                action: "/page"
              })}>
                Fetch
              </button>
              {fetcher.data ? <pre data-fetcher-data>{fetcher.data}</pre> : null}
            </>
          );
        }
      `,
    },
  });

  let logs: string[] = [];
  page.on("console", (msg) => logs.push(msg.text()));

  let appFixture = await createAppFixture(fixture);
  let app = new PlaywrightFixture(appFixture, page);

  await app.goto("/", true);
  await page.click('a[href="/page"]');
  await page.waitForSelector("[data-page]");

  expect(await app.getHtml()).toContain("hello world");
  expect(logs).toEqual([
    'start navigate [["currentUrl","/"],["to","/page"]]',
    "start loader root /page",
    "start loader routes/page /page",
    "end loader root /page",
    "end loader routes/page /page",
    'end navigate [["currentUrl","/"],["to","/page"]]',
  ]);
  logs.splice(0);

  await page.click("[data-fetch]");
  await page.waitForSelector("[data-fetcher-data]");
  await expect(page.locator("[data-fetcher-data]")).toContainText("OK");
  expect(logs).toEqual([
    'start fetch [["body",{"key":"value"}],["currentUrl","/page"],["fetcherKey","a"],["formData",null],["formEncType","application/x-www-form-urlencoded"],["formMethod","post"],["href","/page"]]',
    "start action routes/page /page",
    "end action routes/page /page",
    "start loader root /page",
    "start loader routes/page /page",
    "end loader root /page",
    "end loader routes/page /page",
    'end fetch [["body",{"key":"value"}],["currentUrl","/page"],["fetcherKey","a"],["formData",null],["formEncType","application/x-www-form-urlencoded"],["formMethod","post"],["href","/page"]]',
  ]);

  appFixture.close();
});
